﻿using Microsoft.IdentityModel.Tokens;
using RuoVea.ExConfig;
using RuoVea.ExUtil;
using RuoVea.ExUtil.Exceptions;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace RuoVea.ExWeb.Jwt
{
    /// <summary>
    /// jwt
    /// </summary>
    public class JwtHelper
    {

        /// <summary>
        /// 参数校验
        /// </summary>
        /// <param name="iss"></param>
        /// <param name="aud"></param>
        /// <param name="secret"></param>
        static void Volatile(string iss,string aud,  string secret)
        {
            if (string.IsNullOrWhiteSpace(aud))
                throw new ArgumentNullException("aud不能为空.");

            if (string.IsNullOrWhiteSpace(iss))
                throw new ArgumentNullException("iss不能为空.");

            if (string.IsNullOrWhiteSpace(secret))
                throw new ArgumentNullException("secret不能为空.");
       }
        static void Volatile(string iss)
        {
            if (string.IsNullOrWhiteSpace(iss))
                throw new ArgumentNullException("iss不能为空.");
        }
        #region 颁发JWT字符串
        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJwt(TokenModel tokenModel)
        {
            string iss = Appsettings.app("JwtSetting", "Issuer").ToString();
            string aud = Appsettings.app("JwtSetting", "Audience").ToString();
            string secret = Appsettings.app("JwtSetting", "SecretKey").ToString();
            Volatile(iss, aud, secret);

            long validfor = 10 * 60;

            var identity = new ClaimsIdentity(new GenericIdentity(tokenModel.Id?.ToString(), "Token"));

            //identity.AddClaim(new Claim("id", tokenModel.Id.ToString()));
            identity.AddClaim(new Claim(ClaimTypes.Name, tokenModel.Name));

            // 过期时间
            DateTime IssuedAt = DateTime.UtcNow;
            DateTime expires = IssuedAt.Add(TimeSpan.FromSeconds(validfor));

            var claims = new List<Claim>
            {
                new Claim(JwtRegisteredClaimNames.Sub, tokenModel.Name),
                new Claim(JwtRegisteredClaimNames.Jti,IdGenerator.Id.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(IssuedAt).ToString(), ClaimValueTypes.Integer64),

                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(IssuedAt).ToUnixTimeSeconds()}") ,
                new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(IssuedAt).ToUnixTimeSeconds()}"),

                new Claim(JwtRegisteredClaimNames.Iss,iss),
                new Claim(JwtRegisteredClaimNames.Aud,aud),
                new Claim(ClaimTypes.Role,tokenModel.Roles),
 
                identity.FindFirst(ClaimTypes.Name),
                //identity.FindFirst("id"),
           };
            if (tokenModel.Claims != null)
            {
                claims.Add(new Claim("Claims", tokenModel.Claims.ToJson()));
            }
            //秘钥 (SymmetricSecurityKey 对安全性的要求，密钥的长度太短会报出异常)
            var securityKey = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)), SecurityAlgorithms.HmacSha256);
            var jwt = new JwtSecurityToken(
                issuer: iss,
                audience: "audience",
                claims: claims,
                notBefore: IssuedAt,
                expires: expires,
                signingCredentials: securityKey
                );
             
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            var response = new
            {
                auth_token = encodedJwt,
                refresh_token = IdGenerator.Id,
                expires_in = (int)TimeSpan.FromSeconds(validfor).TotalSeconds,
                token_type = "Bearer"
            };

            return response.Json2Str();
        }
        #endregion

        #region 刷新token
        /// <summary>
        /// 刷新token
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static string RefreshToken(string jwtStr)
        {
            TokenModel tokenModel = SerializeJwt(jwtStr);
            return IssueJwt(tokenModel);
        }
        #endregion

        #region 解析 jwt
        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenModel SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();

            if (!jwtHandler.CanReadToken(jwtStr))
            {
                return null;
            }

            string iss = Appsettings.app("JwtSetting", "Issuer").ToString();
            Volatile(iss);

            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            if (jwtToken.Issuer != iss)
            {
                return null;//不正确
            }
            if (jwtToken.ValidTo < DateTime.Now)
            {
                return null;//过期
            }
            object role;
            object name;
            try
            {
                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
                jwtToken.Payload.TryGetValue(ClaimTypes.Name, out name);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var tm = new TokenModel
            {
                Id = jwtToken.Id.ToString(),
                Name = name?.ToString(),
                Roles = role != null ? role.ToString() : "",
            };
            return tm;
        }
        #endregion

        #region 时间差
        /// <summary>
        /// 时间差
        /// </summary>
        /// <param name="date"></param>
        /// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>    
        public static long ToUnixEpochDate(DateTime date)
        {
            return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
        }
        #endregion

        #region 获取过期时间
        /// <summary>
        /// 获取过期时间
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static DateTime GetExp(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);

            DateTime expDate = (jwtToken.Payload[JwtRegisteredClaimNames.Exp] ?? 0).ToInt().ToDate();
            return expDate;
        }
        #endregion

        #region 时间是否有效
        /// <summary>
        /// 时间是否有效
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static bool IsExp(string jwtStr)
        {
            return GetExp(jwtStr) < DateTime.Now;
        }
        #endregion

        #region 获取用户Id
        /// <summary>
        /// 获取用户Id
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static long GetUserId(string jwtStr)
        {
            try
            {
                return new JwtSecurityTokenHandler().ReadJwtToken(jwtStr).Id.ToInt();
            }
            catch
            {
                return 0;
            }
        } 
        #endregion
    }
}
